﻿using System.Collections;
using System;

public abstract class ColEntidades
{
    #region Atributos de la clase

    protected ArrayList miColeccion;
    protected ArrayList miColeccionEliminados;

    #endregion

    #region Propiedades públicas

    public IEnumerable MiColeccion
    {
        get { return this.miColeccion; }
    }

    #endregion

    #region Constructores

    /**
        * \brief Constructor de ColEntidades.
        * \return La ColEntidades creado.
        */
    public ColEntidades() 
    { 
        this.miColeccion = new ArrayList();
        this.miColeccionEliminados = new ArrayList();
    }

    /**
        * \brief Constructor de copia de ColEntidades.
        * \param pColEntidades ColEntidades sobre el que realizar la copia
        * \return ColEntidades creado y listo para usar.
        */
    public ColEntidades(ColEntidades pColEntidades)
    {
        this.miColeccion = new ArrayList();
        foreach (Entidad entidad in pColEntidades)
            this.añadir(entidad);
    }

    #endregion

    #region Métodos de operación

    /**
    * \brief Función que comprueba si una entidad esta dentro de una colección.
    * \pre La entidad tiene que ser correcto.
    * \param pElemento - entidad que se va a comprobar si se encuentra dentro de una colección.
    * \return TRUE si el elemento pertenece a la colección y FALSE si el elemento no pertenece a la colección.
    */
    protected bool existe(Entidad pElemento)
    {
        return this.miColeccion.Contains(pElemento);
    }
    /**
    * \brief Función que añade una entidad a una colección.
    * \pre La entidad tiene que ser correcto.
    * \param pElemento - entidad que se va a añadir.
    */
    protected void añadir(Entidad pElemento)
    {
        if (pElemento != null && !this.existe(pElemento))
            this.miColeccion.Add(pElemento);
    }
    /**
    * \brief Función que añade una colección a la colección.
    * \pre La colección tiene que ser correcta.
    * \param pColeccion - colección que se va a añadir.
    */
    protected void añadirColeccion(ColEntidades pColeccion)
    {
        foreach (Entidad elemento in pColeccion)
            this.añadir(elemento);
    }
    /**
    * \brief Función que elimina una entidad que esta dentro de una colección
    * \pre La entidad tiene que ser correcto.
    * \param pElemento - entidad que se va a eliminar.
    */
    protected void eliminar(Entidad pElemento)
    {
        this.miColeccion.Remove(pElemento);

        if (pElemento != null && !this.miColeccionEliminados.Contains(pElemento))
            this.miColeccionEliminados.Add(pElemento);
    }
    /**
    * \brief Función que elimina una colección.
    * \pre La colección tiene que ser correcta.
    * \param pColeccion - colección que se va a eliminar.
    */
    protected void eliminarColeccion(ColEntidades pColeccion)
    {
        foreach (Entidad elemento in pColeccion)
            this.eliminar(elemento);
    }

    public void vaciarColeccion()
    {
        this.miColeccionEliminados.AddRange(this.miColeccion);
        this.miColeccion.Clear();
    }

    #endregion

    #region Métodos de consulta

    /**
    * \brief Obtiene la entidad que se encuentra en la posición indicada.
    * \param pIndice - Posición de la colección que queremos consultar.
    * \return Devuelve la entidad que se encuentra en la posición indicada.
    */
    public virtual Entidad getEntidadByIndice(int pIndice)
    {
        if (pIndice > (this.Tamaño - 1) || pIndice < 0)
            return null;
        return (Entidad)this.miColeccion[pIndice];
    }

    #endregion

    #region Métodos adicionales

    /**
        * \brief Función que comprueba si una colección está vacía.
        * \return TRUE si la colección está vacía y FALSE en el otro caso.
        */
    virtual public bool coleccionVacia()
    {
        if (this.miColeccion.Count == 0)
            return true;
        else
            return false;
    }
    /**
        * \brief Función que devuelve el enumerador de la coleccion.
        * \return IEnunerator de la colección.
        */
    virtual public IEnumerator GetEnumerator()
    {
        return this.miColeccion.GetEnumerator();
    }      
    /**
    * \brief Función que devuelve el número de elementos de la coleccion.
    * \return Entero correspondiente al tamaño de la colección.
    */
    virtual public int Tamaño
    {
        get { return this.miColeccion.Count; }
    }

    public override bool Equals(object obj)
    {
        if(this.GetType().Equals(obj.GetType()))
        {
            ColEntidades coleccionAComparar = (ColEntidades)obj;

            //Si tienen distinto tamaño automaticamente son distintas
            if (this.Tamaño == coleccionAComparar.Tamaño)
            {
                foreach (Entidad e in this.miColeccion)
                    if (!coleccionAComparar.existe(e))
                        return false;
            }
            else
                return false;

            return true;
        }
        else
            throw new Exception("Error: Método Equals; Clase ColEntidades; No se pueden comparar dos objetos distintos");
    }

    public override int GetHashCode()
    {
        if (this.Tamaño == 0)
            return 0;
        else
        {
            //Definimos un numero entero primo
            int primo = 31;
            int resultado = 1;
            foreach (Entidad e in this.miColeccion)
            {
                resultado = primo * resultado + ((e == null) ? 0 : e.GetHashCode());
            }

            return resultado;
        }            
    }

    internal abstract ColEntidades dameCopiaColeccionEntidades();

    internal virtual void restaurarColeccionEntidades(ColEntidades pColEntidades)
    {
        ArrayList elementoAEliminar = new ArrayList();
        foreach (Entidad e in this.miColeccion)
        {
            Entidad eCopia = pColEntidades.getEntidadByIndice(pColEntidades.miColeccion.IndexOf(e));
            if (eCopia != null) e.restaurarEntidad(eCopia);
            else elementoAEliminar.Add(e);
        }
        foreach (Entidad e in elementoAEliminar)
            this.miColeccion.Remove(e);

        elementoAEliminar.Clear();
        foreach (Entidad e in this.miColeccionEliminados)
        {
            Entidad eCopia = pColEntidades.getEntidadByIndice(pColEntidades.miColeccion.IndexOf(e));
            if (eCopia != null)
            {
                e.restaurarEntidad(eCopia);
                elementoAEliminar.Add(e);
                this.miColeccion.Add(e);
            }
        }
        foreach (Entidad e in elementoAEliminar)
            this.miColeccionEliminados.Remove(e);
    }

    #endregion
}